package com.mapforge.map.tools.tilewriting;

import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;

import com.mapforge.exceptions.ChosenTileLayerException;
import com.mapforge.exceptions.EmptySelectionException;
import com.mapforge.exceptions.TileLayerStateException;
import com.mapforge.map.TileLayer;
import com.mapforge.map.TileMap;
import com.mapforge.map.tilewriter.TileWriter;
import com.mapforge.map.tools.selection.AbstractSelectionTool;
import com.mapforge.map.tools.selection.SelectionToolState;
import com.mapforge.selection.ContinuousSelection;
import com.mapforge.selection.Selection;
import com.mapforge.selection.TileSelection;

/**
 * Edits maps by writing tiles with a tile writer to a tile layer. A tile writer
 * tool is just a selection tool which allows a tile writer to write to its
 * selection.
 * 
 * @author Will Morrison
 * 
 */
public abstract class AbstractTileWriterTool extends AbstractSelectionTool {

	/**
	 * A selection of tiles used to write to tiles in the selected layer with.
	 */
	private TileSelection tileSelection;

	/**
	 * {@link TileWriter} used to edit the current {@link TileLayer}
	 */
	private TileWriter writer;

	public AbstractTileWriterTool(String description) {
		super(description);
	}

	/**
	 * Returns the tile writer used to edit this clip.
	 * 
	 * @return
	 */
	public TileWriter getTileWriter() {
		return writer;
	}

	public void setTileWriter(TileWriter writer) {
		this.writer = writer;
	}

	protected void validateState() throws ChosenTileLayerException,
			TileLayerStateException, EmptySelectionException,
			IllegalStateException {
		if (getTileLayer() == null) {
			throw new ChosenTileLayerException();
		} else if (!getTileLayer().isVisible()) {
			throw new TileLayerStateException("Selected layer is not visible.");
		} else if (getTileLayer().isLocked()) {
			throw new TileLayerStateException("Selected layer is locked.");
		}
	}

	public void setTileMap(TileMap tileMap) {
		super.setTileMap(tileMap);
		this.setTileLayer(null);
	}

	public void paintTilePreview(Graphics2D g) {
		paintTilePreview(g, getAnchorPoint(), getMovingPoint(),
				getCurrentSelection(), tileSelection);
	}

	/** */
	public void setTileSelection(TileSelection tileSelection) {
		this.tileSelection = tileSelection;
	}

	/** */
	public TileSelection getTileSelection() {
		return this.tileSelection;
	}

	public void paintTilePreview(Graphics2D g, Point startingPoint,
			Point currentPoint, Selection destination, TileSelection input) {
		if (destination != null) {
			TileMap tilemap = getTileMap();
			int x = Math.max(tilemap.getClipX() / tilemap.getTileWidth(), 0);
			int y = Math.max(tilemap.getClipY() / tilemap.getTileHeight(), 0);
			Rectangle rect = new Rectangle(x, y, Math.min(tilemap
					.getClipWidth()
					/ tilemap.getTileWidth() + 1, tilemap
					.getTotalHorizontalTiles()
					- x), Math.min(tilemap.getClipHeight()
					/ tilemap.getTileHeight() + 1, tilemap
					.getTotalVerticalTiles()
					- y));
			destination.intersect(new ContinuousSelection(rect));
			getTileWriter().previewWrite(g, input, destination);
		}
	}

	/**
	 * Performs an edit using this tool without disturbing state within the
	 * tool.
	 * 
	 * @param startPoint
	 * @param endPoint
	 * @param layer
	 * @param writer
	 */
	public void performEdit(TileWriterToolState state) {
		Point p = getAnchorPoint();
		Point p2 = this.getMovingPoint();
		TileLayer layer2 = this.getTileLayer();
		TileWriter writer2 = this.getTileWriter();

		state.restoreState(this);
		performEdit();

		setAnchorPoint(p);
		setMovingPoint(p2);
		setTileLayer(layer2);
		setTileWriter(writer2);
	}

	public SelectionToolState getState() {
		return new TileWriterToolState(getAnchorPoint(), getMovingPoint(),
				getTileLayer(), getTileSelection(), getTileWriter());
	}
}
